﻿using Microsoft.Win32;
using System;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Security;
using System.Xml.Linq;
using System.Linq;

namespace HelpViewerRedirector
{
    class Program
    {
        static XNamespace w3ns = XNamespace.Get("http://www.w3.org/1999/xhtml");
        const string serviceURL = "http://services.mtps.microsoft.com/serviceapi/products/";

        static void Main(string[] args)
        {
            if (args.Length > 0)
            {
                if (args[0].StartsWith("ms-xhelp"))
                {
                    try
                    {
                        RedirectHelpRequest(args);
                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine(ex.Message);
                        Console.ReadKey();
                    }
                }
                else
                {
                    //invalid input use output the usage
                    OutputConsoleHeader();
                    OutputConsoleUsage();
                    Console.ReadKey();
                }
            }
            else
            {
                try
                {
                    OutputConsoleHeader();

                    DownloadMSHAFilesFromMicrosoft();

                    if (!IsHelpViewer1Installed())
                    {
                        //MS-XHelp registry key doesn't exist which suggests that help viewer 1.0 isn't installed
                        Console.WriteLine("Cannot find registry key for help viewer 1.0\nHelp Viewer Redirector not registered");
                    }
                    else
                        RegisterOrUnregister();
                }

                catch (SecurityException ex)
                {

                    Console.WriteLine(ex.Message);
                    Console.WriteLine("");

                    Console.WriteLine("Cannot do stuff due to permissions, run as administrator or a user with registry access");
                }
                catch (Exception ex)
                {

                    Console.WriteLine(ex.Message);
                    Console.WriteLine("");
                    Console.WriteLine("Cannot do stuff");
                }
                Console.ReadKey();
            }
        }

        private static bool IsHelpViewer1Installed()
        {
            return Registry.ClassesRoot.OpenSubKey(@"MS-XHelp\shell\open\command") != null;
        }

        private static void RedirectHelpRequest(string[] args)
        {
            string arg1 = args[0];
            string xhelpQuery = arg1.Substring(13);

            //example
            //“ms-xhelp:///?method=f1&query=msdnstart&product=Contoso&productVersion=100&locale=en-US”

            string FullPath;
            string catalogFolder = @"C:\ProgramData\Microsoft\HelpLibrary2\Catalogs";
            if (FindHelpViewerExe(out FullPath))
            {
                if (Directory.Exists (catalogFolder))
                {
                    if (Directory.GetDirectories(catalogFolder).Length > 0)
                    {
                        string catalog = Path.GetFileName(Directory.GetDirectories(catalogFolder)[0]);
                        ProcessStartInfo pi = new ProcessStartInfo(FullPath, string.Format("/catalogName {0} /helpQuery {1}", catalog,xhelpQuery));
                        System.Diagnostics.Process.Start(pi);
                    }
                    else
                        throw new Exception(string.Format("Can't find the catalog in help viewer catalog folder \n{0}", catalogFolder));

                }
                            else
            {
                throw new Exception(string.Format("Can't find the help viewer catalog folder \n{0}",catalogFolder));
            }

            }
            else
            {
                throw new Exception("Can't find the help viewer 2.0 executable");
            }
        }

        private static bool FindHelpViewerExe(out string FullPath)
        {
            //Find Help Viewer
            
            string[] versionPaths = new string[]{
                 @"{0}{1}\Microsoft Help Viewer\v2.1\HlpViewer.exe"
                ,@"{0}{1}\Microsoft Help Viewer\v2.0\HlpViewer.exe"
                 };
            string[] progFolders = new string[]{
                "Program Files (x86)"
                ,"Program Files"
            };
            FullPath = "";
            foreach (var path in versionPaths)
            {
                foreach (var ProgFolder in progFolders)
                {
                    foreach (var drive in DriveInfo.GetDrives())
                    {
                        FullPath = string.Format(path, drive.Name, ProgFolder);
                        if (File.Exists(FullPath))
                        {
                            return true;
                        }
                    }
                }


            }
            return false ;
        }

        private static void RegisterOrUnregister()
        {
            var MSXHelpKey = Registry.ClassesRoot.OpenSubKey(@"MS-XHelp\shell\open\command",true);
            var DDExecKey = Registry.ClassesRoot.OpenSubKey(@"MS-XHelp\shell\open\ddeexec\application",true);

            //check to see if the command sub key is set to the HelpLibAgent
            var val = MSXHelpKey.GetValue("");
            if (val.ToString().Contains("HelpLibAgent"))
            {
                RegisterApplication(MSXHelpKey, DDExecKey, val);
            }
            else
            {
                UnregisterApplication(MSXHelpKey, DDExecKey);
            }

        }

        private static void UnregisterApplication(RegistryKey MSXHelpKey, RegistryKey DDExecKey)
        {
            Console.WriteLine("Unregistering for help viewer requests");

            var OpenBackupValue = (string)MSXHelpKey.GetValue("backup", "novalue");
            if (OpenBackupValue != "novalue")
            {
                MSXHelpKey.SetValue("", OpenBackupValue, RegistryValueKind.String);
                MSXHelpKey.DeleteValue("backup");
            }
            //Uninstall
            if (DDExecKey != null)
            {
                var BackupValue = (string)DDExecKey.GetValue("backup", "novalue");
                if (BackupValue != "novalue")
                    DDExecKey.SetValue("", "HelpLibAgent", RegistryValueKind.String);
            }
            Console.WriteLine("Unregistering complete");

        }

        private static void RegisterApplication(RegistryKey MSXHelpKey, RegistryKey DDExecKey, object val)
        {
            Console.WriteLine("Getting path to the help viewer redirector ");
            var HelpViewerPath = GetExePath();
            Console.WriteLine("Registering \"{0}\" as target for help viewer requests", HelpViewerPath);

            //HelpViewerRedirector is not registered
            //Copy the value to the backup node 
            MSXHelpKey.SetValue("backup", val, RegistryValueKind.String);
            //    var thisProc = System.Diagnostics.Process.GetCurrentProcess();

            MSXHelpKey.SetValue("", string.Format("\"{0}\" \"%1\"", HelpViewerPath), RegistryValueKind.String);
            if (DDExecKey != null)
            {
                DDExecKey.SetValue("backup", DDExecKey.GetValue("", ""), RegistryValueKind.String);
                DDExecKey.SetValue("backup", "HelpViewerRedirector", RegistryValueKind.String);
            }
        }

        private static string GetExePath()
        {
            var thisProc = System.Diagnostics.Process.GetCurrentProcess();

            var HelpViewerPath = thisProc.MainModule.FileName;
            return HelpViewerPath;
        }

        private static void DownloadMSHAFilesFromMicrosoft()
        {
            if (CanYouAccessTheDownloadSite())
            {
                try
                {


                    Console.WriteLine("Downloading BOL configuration files from MS");

                    Console.WriteLine("Loading available Books for Help Viewer to find SQL Server books");
                    var prodGroupList = XDocument.Load(serviceURL);

                    var SQLProdGroupNode = (from d in prodGroupList.Descendants(w3ns + "div")
                                            where d.Attribute("class").Value == "product-group"

                                            && d.Descendants(w3ns + "span").Any(s => s.Value == "SQL Server")
                                            select d).ToList();

                    if (SQLProdGroupNode.Count == 0)
                        throw new Exception(string.Format("Can't find SQL Server Product group check the service URL contains SQL Server\n,{0}", serviceURL));

                    //get the products from the product group URL
                    var prodGroupRelURL = GetLinkFromNode(SQLProdGroupNode[0], "product-group-link");
                    if (prodGroupRelURL == null)
                        throw new Exception(string.Format("Can't find URL for the product group looking for href \nof first anchor in the SQL Server Product\ncheck the service URL contains SQL Server\n,{0}", serviceURL));

                    var prodGroupURL = Path.Combine(serviceURL, prodGroupRelURL);

                    Console.WriteLine("Loading available SQL Server books");
                    var prodList = XDocument.Load(prodGroupURL);
                    var SQLProdNodes = (from d in prodList.Descendants(w3ns + "div")
                                        where d.Attribute("class").Value == "product"
                                        select d).ToList();

                    foreach (var SQLProd in SQLProdNodes)
                    {
                        //Process a SQL Product
                        var ProdName = SQLProd.Elements(w3ns + "span").Where(a => a.Attribute("class").Value == "name").FirstOrDefault().Value;
                        Console.WriteLine("Processing books for {0}", ProdName);
                        var ProdRelURL = GetLinkFromNode(SQLProd, "product-link");

                        var ProdURL = Path.Combine(serviceURL + ProdRelURL);

                        var bookList = XDocument.Load(ProdURL);
                        var BookNodes = (from d in bookList.Descendants(w3ns + "div")
                                         where d.Attribute("class").Value == "book"
                                         select d).ToList();
                        foreach (var book in BookNodes)
                        {
                            SaveBookToMSHA(serviceURL, ProdName, book);

                        }

                    }
                    Console.WriteLine("Download Complete\n");
                }
                catch (Exception ex)
                {

                    Console.WriteLine("Error - {0}",ex.Message);

                }
            }
        }

        private static bool CanYouAccessTheDownloadSite()
        {
            try
            {
                var wc = HttpWebRequest.Create(serviceURL);
                var resp = (HttpWebResponse)wc.GetResponse();
                if (!(resp.StatusCode == HttpStatusCode.OK
                    || resp.StatusCode == HttpStatusCode.NotModified))
                    throw new Exception(string.Format("Invalid Response code  - {0}", resp.StatusCode));
            }
            catch (Exception ex)
            {
                Console.WriteLine("Can't access the BOL download service\n{0}\nError - {1}", serviceURL, ex.Message);
                return false;
            }
            return true;
        }

        private static void SaveBookToMSHA(string serviceURL,  string ProdName, XElement book)
        {
            string rootFolder = Path.GetDirectoryName(GetExePath());
                            
            var BookName = string.Format("{0}_{1}", ProdName, book.Elements(w3ns + "span").Where(a => a.Attribute("class").Value == "name").FirstOrDefault().Value);
            Console.WriteLine("{0} - Generating import file", BookName);
            var BookRelURL = GetLinkFromNode(book, "book-link");

            var BookLocalFile = Path.ChangeExtension(Path.Combine(rootFolder, BookName), ".msha");
            var bookURL = Path.Combine(serviceURL, BookRelURL);

            var xe = XDocument.Load(bookURL);

            Console.WriteLine("{0} - Correcting file for Help Viewer 2.0",BookName);
            
            
            var BodyElement = xe.Descendants(w3ns + "body").FirstOrDefault();
            BodyElement.SetAttributeValue("class", "vendor-book");

            var DetailsDiv = from body in xe.Descendants(w3ns+"body")
                             from div in body.Descendants(w3ns + "div")
                                 where div.Attribute("class").Value =="details"
                                 select div ;
            if (DetailsDiv.Count()==0)
                throw new Exception ("Can't find Book Details Div to add the product");
            
            var ProdElement = new XElement (w3ns+"span");
            ProdElement.SetAttributeValue("class","product");
            ProdElement.SetValue(ProdName);

            var NameElement = DetailsDiv.Elements(w3ns + "span").Where(e => e.Attribute("class").Value == "name").FirstOrDefault();
            NameElement.Value = BookName;

            DetailsDiv.First().FirstNode.AddAfterSelf(ProdElement);

            Console.WriteLine("{0} - Saving file", BookName);
            xe.Save(BookLocalFile);
        }

        private static string GetLinkFromNode( XElement book, string linkName)
        {
            var BookRelURL = book.Elements(w3ns + "a").Where(a => a.Attribute("class").Value == linkName).FirstOrDefault().Attribute("href").Value;
            return BookRelURL;
        }


        private static void OutputConsoleUsage()
        {
            Console.WriteLine("Usage: HelpViewerRedirector [<MS-XHelp URL>]");
            Console.WriteLine("");
            Console.WriteLine("Example: HelpViewerRedirector \"ms-xhelp:///?method=f1&query=msdnstart&product=Contoso&productVersion=100&locale=en-US\"");
            Console.WriteLine("");
            Console.WriteLine("If called with no arguments the application register or unregisters itself as the target for the MS-XHelp URL");

        }

private static void OutputConsoleHeader()
{
                Console.WriteLine("Help Viewer redirector");
                Console.WriteLine("Copyright (c) Sabin.io. 2014");
                Console.WriteLine("");
                
}


    }
}
